package ms.jasim;

// Environment code for project Jasim

import jason.asSyntax.Literal;
import jason.asSyntax.PredicateIndicator;
import jason.asSyntax.StringTerm;
import jason.asSyntax.Structure;
import jason.asSyntax.Term;
import jason.bb.BeliefBase;
import jason.environment.Environment;
import jason.infra.centralised.CentralisedAgArch;
import jason.infra.centralised.RunCentralisedMAS;
import jason.runtime.RuntimeServicesInfraTier;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import ms.jacrim.framework.ActiveSolution;
import ms.jacrim.framework.Event;
import ms.jacrim.framework.Model;
import ms.jacrim.framework.XmlConfiguration;
import ms.jacrim.framework.PddlModel.Actor;
import ms.jacrim.framework.SolutionManager.ActionStatus;
import ms.jacrim.pddl.PddlSolution;

public class JasimEnv extends Environment {

	private static final long serialVersionUID = -4485437743008605029L;

	public static final String JASIM_AGENT = "jasim";
	Logger logger = Logger.getLogger("Jasim." + JasimEnv.class.getName());
	public final Model Model;
	protected RuntimeServicesInfraTier RT;
	private JasimServer server;

	public JasimEnv() throws Exception {
		System.setProperty("java.security.policy", "java.policy");

		logger.info("Creating JasimModel...");
		Model = ms.jacrim.framework.Model.createModel();

		logger.info("Creating JasimServer...");
		server = new JasimServer(this);
		server.run();
	}

	/** Called before the MAS execution with the args informed in .mas2j */
	@Override
	public void init(String[] args) {
		super.init(args);
		RT = this.getEnvironmentInfraTier().getRuntimeServices();
	}

	@Override
	public boolean executeAction(String agName, Structure action) {

		String actName = action.getFunctor();
		try {
			java.lang.reflect.Method med = this.getClass().getMethod(actName, String.class, Structure.class);
			if (med != null) {
				// System.out.printf("EXECUTING: [%s] at %s\n", actName, new
				// Date());
				Object result = med.invoke(this, agName, action);
				// System.out.printf("EXECUTED: [%s] at %s\n", actName, new
				// Date());
				if (result != null && result instanceof Boolean)
					return (Boolean) result;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return true;

		// if (JASIM_AGENT.equalsIgnoreCase(agName)) {
		// if ("start".equalsIgnoreCase(actName))
		// startSimulation();
		// else if ("replan".equalsIgnoreCase(actName))
		// replan();
		// else if ("create_agents".equalsIgnoreCase(actName))
		// introduceAgents();
		// }
		// if ("notify".equalsIgnoreCase(action.getFunctor())) {
		// String agentName = action.getTerm(0).toString();
		// String eventCode = action.getTerm(1).toString();
		// String eventData = action.getTerm(2).toString();
		// if (server != null) {
		// server.raiseEvent(new JasimEventArgument(agentName, 1, new String[] {
		// eventCode, eventData }));
		// }
		// } else if ("mark".equalsIgnoreCase(actName)) {
		// Term act = action.getTerm(0);
		// String status = action.getTerm(1).toString().toUpperCase();
		// if (act.isLiteral())
		// setActionStatus((Literal) act, ActionStatus.valueOf(status));
		// } else if ("ra_add".equalsIgnoreCase(actName))
		// failrecover(true, action);
		// else if ("ra_remove".equalsIgnoreCase(actName))
		// failrecover(false, action);
		// else
		// logger.info(String.format("agent [%s] perfom action %s", agName,
		// action.getFunctor()));

		// return true;
	}

	public void start_planer(String agName, Structure action) throws Exception {
		System.out.println("planer: " + new Date());
		logger.info("Start planner...");
		Event ev = new Event("Start planner");
		Model.Events.add(ev);
		List<PddlSolution> sols = Model.SolutionManager.collectSolutions(true);
		Model.SolutionManager.setActiveSolution(sols.get(0));
		ev.setEnd(new Date());
	}

	public void introduce_agents(String agName, Structure action) throws Exception {
		System.out.println("intro: " + new Date());
		ActiveSolution sol = Model.SolutionManager.ActiveSolution;
		XmlConfiguration config = Model.Context.getService(XmlConfiguration.class);
		String aslFile = config.get("agent_asl");

		if (!sol.Available())
			return;

		// clear current plan from agent
		Set<String> agents = RT.getAgentsNames();
		for (String ag : agents) {
			BeliefBase bb = getAgentBB(ag);
			if (bb != null)
				bb.abolish(new PredicateIndicator("plan", 1));
		}
		
		// setup new plan by directly adding solution' actions to agents
		for (int i = 0; i < sol.ActionCount(); i++) {
			PddlSolution.Action a = sol.getAction(i);

			String actionName = a.Name().toLowerCase();
			String agentName = actionName.startsWith("combine") ? JASIM_AGENT : a.Argument(0).toLowerCase();

			if (!RT.getAgentsNames().contains(agentName)) {
				logger.info(String.format("Create agent: %s...", agentName));
				RT.createAgent(agentName, aslFile, null, null, null, null);
			}

			BeliefBase bb = getAgentBB(agentName);
			
			SimpleDateFormat df = new SimpleDateFormat("yyMMddHHmmss");
			
			Literal l = Literal.parseLiteral(String.format("plan(%s)", PddlActionToLiteral(a)));
			l.addAnnot(Literal.parseLiteral(String.format("time(%s)", df.format(new Date()))));
			
			bb.add(l);
		}
		
		informAgsEnvironmentChanged();
	}

	public void request_goals(String agName, Structure action) {
		Actor a = Model.PddlModel.Actors().find(agName.toUpperCase());
		if (a != null) {
			String[] requests = a.getRequestGoals();
			if (requests != null)
				for (String r : requests)
					addPercept(a.Id().toLowerCase(), Literal.parseLiteral(String.format("request(%s)", r.toLowerCase())));
		}
		else
			System.out.printf("Cannot file actor %s.\n", agName);
	}

	public void mark(String agName, Structure action) {
		Term act = action.getTerm(0);
		String status = action.getTerm(1).toString().toUpperCase();
		if (act.isLiteral()) {
			ActiveSolution.ActionInfo info = Model.SolutionManager.ActiveSolution.getActionInfo(act.toString());
			if (info != null) {
				ActionStatus s = ActionStatus.valueOf(status);
				info.setStatus(s);
				if (s == ActionStatus.EXECUTING)
					Model.Events.add(new Event(info));
			}
		}
		notifyClient(agName, action);
	}
	
	public void clear_percepts(String agName, Structure action) {
		clearPercepts(agName);
	}
	
	public void remove_percept(String agName, Structure action) {
		
		Literal l = (Literal) action.getTerm(action.getArity() - 1);
		boolean local = action.getArity() == 1 || action.getTerm(0).toString().equalsIgnoreCase("global");
		if (local)
			removePercept(agName, l);
		else
			removePercept(l);
	}
	
	public void remove_percepts(String agName, Structure action) {

		Literal l = (Literal) action.getTerm(action.getArity() - 1);
		boolean local = action.getArity() == 1 || action.getTerm(0).toString().equalsIgnoreCase("global");
		if (local)
			removePerceptsByUnif(agName, l);
		else
			removePerceptsByUnif(l);
	}
	
	public void ra_add(String agName, Structure action) {
		failrecover(true, action);
	}
	
	public void ra_remove(String agName, Structure action) {
		failrecover(false, action);
	}
	
	public void notifyClient(String agName, Structure action) {
		// LOG: notify agent
		// System.out.println("Notify client: " + action.getTerm(0).toString());
		server.notifyClient(String.format("ag=[%s] ev=[%s]", agName, action.getTerm(0).toString()));
	}
	
	public BeliefBase getAgentBB(String agName) {
		CentralisedAgArch arch = RunCentralisedMAS.getRunner().getAg(agName);
		return arch.getUserAgArch().getTS().getAg().getBB();
	}

	String TermToPddlPredicate(Term t) {
		String result = null;
		if (t.isLiteral()) {
			Literal l = (Literal) t;
			result = l.getFunctor();
			if (l.getArity() > 0)
				for (Term arg : l.getTerms())
					result += " " + TermToPddlPredicate(arg);
			else
				result = result.toUpperCase();
		} else if (t.isString()) {
			StringTerm st = (StringTerm) t;
			result = st.getString();
		} else
			result = t.toString();
		return result.trim();
	}

	String PddlActionToLiteral(PddlSolution.Action a) {
		StringBuilder literal = new StringBuilder(50);
		literal.append(a.Name().toLowerCase());
		literal.append("(");
		for (int j = 0; j < a.ArgumentCount(); j++) {
			if (j > 0)
				literal.append(", ");
			literal.append(a.Argument(j).toLowerCase());
		}
		literal.append(")");
		
		return literal.toString();
	}

	private void failrecover(boolean add, Structure action) {
		String obj = action.getTerm(0).toString();
		String literal = "";
		for (int i = 1; i < action.getArity(); i++)
			literal += TermToPddlPredicate(action.getTerm(i)) + " ";
		String cmd = null;
		if ("fact".equalsIgnoreCase(obj))
			cmd = add ? "+@" : "-@";
		else if ("goal".equalsIgnoreCase(obj))
			cmd = add ? "+!" : "-!";
		Model.PddlModel.addPostCommand(cmd + literal.replace(" )", ")").trim());
		System.out.println(cmd + literal);
	}

	/** Called before the end of MAS execution */
	@Override
	public void stop() {
		server.shutdown();
		super.stop();
	}
}
